// 验证方法
import test from './test.js';
// 防抖方法
import debounce from './debounce.js'
// 节流方法
import throttle from './throttle.js'

/**
 * @description 进行延时，以达到可以简写代码的目的 比如: await uni.$u.sleep(20)将会阻塞20ms
 * @param {number} value 堵塞时间 单位ms 毫秒
 * @returns {Promise} 返回promise
 */
function sleep(value = 30) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve()
        }, value)
    })
}

/**
 * @description 取一个区间数
 * @param {Number} min 最小值
 * @param {Number} max 最大值
 */
function random(min, max) {
    if (min >= 0 && max > 0 && max >= min) {
        const gab = max - min + 1
        return Math.floor(Math.random() * gab + min)
    }
    return 0
}

/**
 * @param {Number} len uuid的长度
 * @param {Boolean} firstU 将返回的首字母置为"u"
 * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
 */
function guid(len = 32, firstU = true, radix = null) {
    const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
    const uuid = []
    radix = radix || chars.length

    if (len) {
        // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
        for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
    } else {
        let r
        // rfc4122标准要求返回的uuid中,某些位为固定的字符
        uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
        uuid[14] = '4'

        for (let i = 0; i < 36; i++) {
            if (!uuid[i]) {
                r = 0 | Math.random() * 16
                uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
            }
        }
    }
    // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
    if (firstU) {
        uuid.shift()
        return `u${uuid.join('')}`
    }
    return uuid.join('')
}

/**
 * @description 深度克隆
 * @param {object} obj 需要深度克隆的对象
 * @param cache 缓存
 * @returns {*} 克隆后的对象或者原值（不是对象）
 */
function deepClone(obj, cache = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') return obj;
    if (cache.has(obj)) return cache.get(obj);
    let clone;
    if (obj instanceof Date) {
        clone = new Date(obj.getTime());
    } else if (obj instanceof RegExp) {
        clone = new RegExp(obj);
    } else if (obj instanceof Map) {
        clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
    } else if (obj instanceof Set) {
        clone = new Set(Array.from(obj, value => deepClone(value, cache)));
    } else if (Array.isArray(obj)) {
        clone = obj.map(value => deepClone(value, cache));
    } else if (Object.prototype.toString.call(obj) === '[object Object]') {
        clone = Object.create(Object.getPrototypeOf(obj));
        cache.set(obj, clone);
        for (const [key, value] of Object.entries(obj)) {
            clone[key] = deepClone(value, cache);
        }
    } else {
        clone = Object.assign({}, obj);
    }
    cache.set(obj, clone);
    return clone;
}

/**
 * @description JS对象深度合并
 * @param {object} target 需要拷贝的对象
 * @param {object} source 拷贝的来源对象
 * @returns {object|boolean} 深度合并后的对象或者false（入参有不是对象）
 */
function deepMerge(target = {}, source = {}) {
    target = deepClone(target)
    if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
    const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
    for (const prop in source) {
        if (!source.hasOwnProperty(prop)) continue;
        const sourceValue = source[prop];
        const targetValue = merged[prop];
        if (sourceValue instanceof Date) {
            merged[prop] = new Date(sourceValue);
        } else if (sourceValue instanceof RegExp) {
            merged[prop] = new RegExp(sourceValue);
        } else if (sourceValue instanceof Map) {
            merged[prop] = new Map(sourceValue);
        } else if (sourceValue instanceof Set) {
            merged[prop] = new Set(sourceValue);
        } else if (typeof sourceValue === 'object' && sourceValue !== null) {
            merged[prop] = deepMerge(targetValue, sourceValue);
        } else {
            merged[prop] = sourceValue;
        }
    }
    return merged;
}

/**
 * @description 打乱数组
 * @param {array} array 需要打乱的数组
 * @returns {array} 打乱后的数组
 */
function randomArray(array = []) {
    // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
    return array.sort(() => Math.random() - 0.5)
}

/**
 * @description 时间戳转为多久之前
 * @param {String|Number} timestamp 时间戳
 * @param {String|Boolean} format
 * 格式化规则如果为时间格式字符串，超出一定时间范围，返回固定的时间格式；
 * 如果为布尔值false，无论什么时间，都返回多久以前的格式
 * @returns {string} 转化后的内容
 */
function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
    if (timestamp == null) timestamp = Number(new Date())
    timestamp = parseInt(timestamp)
    // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
    if (timestamp.toString().length == 10) timestamp *= 1000
    let timer = (new Date()).getTime() - timestamp
    timer = parseInt(timer / 1000)
    // 如果小于5分钟,则返回"刚刚",其他以此类推
    let tips = ''
    switch (true) {
        case timer < 300:
            tips = '刚刚'
            break
        case timer >= 300 && timer < 3600:
            tips = `${parseInt(timer / 60)}分钟前`
            break
        case timer >= 3600 && timer < 86400:
            tips = `${parseInt(timer / 3600)}小时前`
            break
        case timer >= 86400 && timer < 2592000:
            tips = `${parseInt(timer / 86400)}天前`
            break
        default:
            // 如果format为false，则无论什么时间戳，都显示xx之前
            if (format === false) {
                if (timer >= 2592000 && timer < 365 * 86400) {
                    tips = `${parseInt(timer / (86400 * 30))}个月前`
                } else {
                    tips = `${parseInt(timer / (86400 * 365))}年前`
                }
            } else {
                tips = timeFormat(timestamp, format)
            }
    }
    return tips
}

/**
 * Parse the time to string
 * @param {(Object|string|number)} time
 * @param {string} cFormat
 * @returns {string | null}
 */
function parseTime(time, cFormat) {
    if (arguments.length === 0) {
        return null
    }
    const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
    let date
    if (typeof time === 'object') {
        date = time
    } else {
        if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
            time = parseInt(time)
        }
        if ((typeof time === 'number') && (time.toString().length === 10)) {
            time = time * 1000
        }
        date = new Date(time)
    }
    const formatObj = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay()
    }
    const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
        const value = formatObj[key]
        // Note: getDay() returns 0 on Sunday
        if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
        return value.toString().padStart(2, '0')
    })
    return time_str
}

/**
 * @param {number} time
 * @param {string} option
 * @returns {string}
 */
function formatTime(time, option) {
    if (('' + time).length === 10) {
        time = parseInt(time) * 1000
    } else {
        time = +time
    }
    const d = new Date(time)
    const now = Date.now()

    const diff = (now - d) / 1000

    if (diff < 30) {
        return '刚刚'
    } else if (diff < 3600) {
        // less 1 hour
        return Math.ceil(diff / 60) + '分钟前'
    } else if (diff < 3600 * 24) {
        return Math.ceil(diff / 3600) + '小时前'
    } else if (diff < 3600 * 24 * 2) {
        return '1天前'
    }
    if (option) {
        return parseTime(time, option)
    } else {
        return (
            d.getMonth() +
            1 +
            '月' +
            d.getDate() +
            '日' +
            d.getHours() +
            '时' +
            d.getMinutes() +
            '分'
        )
    }
}

/**
 * @param {string} url
 * @returns {Object}
 */
function param2Obj(url) {
    const search = url.split('?')[1]
    if (!search) {
        return {}
    }
    return JSON.parse(
        '{"' +
        decodeURIComponent(search)
            .replace(/"/g, '\\"')
            .replace(/&/g, '","')
            .replace(/=/g, '":"')
            .replace(/\+/g, ' ') +
        '"}'
    )
}

/**
 * @description 对象转url参数
 * @param {object} data,对象
 * @param {Boolean} isPrefix,是否自动加上"?"
 * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
 */
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
    const prefix = isPrefix ? '?' : ''
    const _result = []
    if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
    for (const key in data) {
        const value = data[key]
        // 去掉为空的参数
        if (['', undefined, null].indexOf(value) >= 0) {
            continue
        }
        // 如果值为数组，另行处理
        if (value.constructor === Array) {
            // e.g. {ids: [1, 2, 3]}
            switch (arrayFormat) {
                case 'indices':
                    // 结果: ids[0]=1&ids[1]=2&ids[2]=3
                    for (let i = 0; i < value.length; i++) {
                        _result.push(`${key}[${i}]=${value[i]}`)
                    }
                    break
                case 'brackets':
                    // 结果: ids[]=1&ids[]=2&ids[]=3
                    value.forEach((_value) => {
                        _result.push(`${key}[]=${_value}`)
                    })
                    break
                case 'repeat':
                    // 结果: ids=1&ids=2&ids=3
                    value.forEach((_value) => {
                        _result.push(`${key}=${_value}`)
                    })
                    break
                case 'comma':
                    // 结果: ids=1,2,3
                    let commaStr = ''
                    value.forEach((_value) => {
                        commaStr += (commaStr ? ',' : '') + _value
                    })
                    _result.push(`${key}=${commaStr}`)
                    break
                default:
                    value.forEach((_value) => {
                        _result.push(`${key}[]=${_value}`)
                    })
            }
        } else {
            _result.push(`${key}=${value}`)
        }
    }
    return _result.length ? prefix + _result.join('&') : ''
}

/**
 * @description 数字格式化
 * @param {number|string} number 要格式化的数字
 * @param {number} decimals 保留几位小数
 * @param {string} decimalPoint 小数点符号
 * @param {string} thousandsSeparator 千分位符号
 * @returns {string} 格式化后的数字
 */
function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
    number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
    const n = !isFinite(+number) ? 0 : +number
    const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
    const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
    const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
    let s = ''

    s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
    const re = /(-?\d+)(\d{3})/
    while (re.test(s[0])) {
        s[0] = s[0].replace(re, `$1${sep}$2`)
    }

    if ((s[1] || '').length < prec) {
        s[1] = s[1] || ''
        s[1] += new Array(prec - s[1].length + 1).join('0')
    }
    return s.join(dec)
}

/**
 * @description 日期的月或日补零操作
 * @param {String} value 需要补零的值
 */
function padZero(value) {
    return `00${value}`.slice(-2)
}

/**
 * @description 获取某个对象下的属性，用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
 * @param {object} obj 对象
 * @param {string} key 需要获取的属性字段
 * @returns {*}
 */
function getProperty(obj, key) {
    if (!obj) {
        return
    }
    if (typeof key !== 'string' || key === '') {
        return ''
    }
    if (key.indexOf('.') !== -1) {
        const keys = key.split('.')
        let firstObj = obj[keys[0]] || {}

        for (let i = 1; i < keys.length; i++) {
            if (firstObj) {
                firstObj = firstObj[keys[i]]
            }
        }
        return firstObj
    }
    return obj[key]
}

/**
 * @description 设置对象的属性值，如果'a.b.c'的形式进行设置
 * @param {object} obj 对象
 * @param {string} key 需要设置的属性
 * @param {string} value 设置的值
 */
function setProperty(obj, key, value) {
    if (!obj) {
        return
    }
    // 递归赋值
    const inFn = function (_obj, keys, v) {
        // 最后一个属性key
        if (keys.length === 1) {
            _obj[keys[0]] = v
            return
        }
        // 0~length-1个key
        while (keys.length > 1) {
            const k = keys[0]
            if (!_obj[k] || (typeof _obj[k] !== 'object')) {
                _obj[k] = {}
            }
            const key = keys.shift()
            // 自调用判断是否存在属性，不存在则自动创建对象
            inFn(_obj[k], keys, v)
        }
    }

    if (typeof key !== 'string' || key === '') {

    } else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
        const keys = key.split('.')
        inFn(obj, keys, value)
    } else {
        obj[key] = value
    }
}

/**
 * @description 去除空格
 * @param String str 需要去除空格的字符串
 * @param String pos both(左右)|left|right|all 默认both
 */
function trim(str, pos = 'both') {
    str = String(str)
    if (pos == 'both') {
        return str.replace(/^\s+|\s+$/g, '')
    }
    if (pos == 'left') {
        return str.replace(/^\s*/, '')
    }
    if (pos == 'right') {
        return str.replace(/(\s*$)/g, '')
    }
    if (pos == 'all') {
        return str.replace(/\s+/g, '')
    }
    return str
}

//往url里添加参数
function addUrlParam(url, arg, arg_val) {
    var pattern = new RegExp(arg + '=([^&]*)');
    var replaceText = arg + '=' + arg_val;
    if (url.match(pattern)) {
        // 使用正则表达式直接替换匹配的参数  
        url = url.replace(pattern, replaceText);
    } else {
        if (url.includes('?')) {
            // 如果 URL 中已经包含 '?'，则在 '?' 后面添加 '&' 和新的参数  
            url += '&' + replaceText;
        } else {
            // 否则，在 URL 末尾添加 '?' 和新的参数  
            url += '?' + replaceText;
        }
    }
    return url;
}

//判断是否是微信浏览器
function checkIsWechatBrowser() {
    let platform = getPlatform();
    if (platform === 'h5') {
        return (
            navigator.userAgent.toLowerCase().match(/MicroMessenger/i) ==
            'micromessenger'
        );
    } else if (platform === 'mp') {
        return true;
    } else {
        return false;
    }
}

//验证数组是否存在某个值
function inArray(array, search) {
    for (var i in array) {
        if (array[i] == search) {
            return true;
        }
    }
    return false;
}

//循环对象或数组(通用遍历函数)
function each(object, callback) {
    var type = (function () {
        switch (object.constructor) {
            case Object:
                return 'Object';
                break;
            case Array:
                return 'Array';
                break;
            case NodeList:
                return 'NodeList';
                break;
            default:
                return 'null';
                break;
        }
    })();
    // 为数组或类数组时, 返回: index, value
    if (type === 'Array' || type === 'NodeList') {
        // 由于存在类数组NodeList, 所以不能直接调用every方法
        [].every.call(object, function (v, i) {
            return callback.call(v, i, v) === false ? false : true;
        });
    }
    // 为对象格式时,返回:key, value
    else if (type === 'Object') {
        for (var i in object) {
            if (callback.call(object[i], i, object[i]) === false) {
                break;
            }
        }
    }
}

/*
  parameter：
  num：格式化目标数字
  decimal：保留几位小数，默认2位
  split：千分位分隔符，默认为,
  moneyFormat(123456789.87654321, 2, ',') // 123,456,789.88
*/
function moneyFormat(num, decimal = 2, split = ',') {

    if (isFinite(num)) { // num是数字
        if (num === 0) { // 为0
            return num.toFixed(decimal);
        } else { // 非0
            var res = '';
            var dotIndex = String(num).indexOf('.');
            if (dotIndex === -1) { // 整数
                res = String(num).replace(/(\d)(?=(?:\d{3})+$)/g, `$1${split}`) + '.' + '0'.repeat(decimal);
            } else { // 非整数
                // js四舍五入 Math.round()：正数时4舍5入，负数时5舍6入
                // Math.round(1.5) = 2
                // Math.round(-1.5) = -1
                // Math.round(-1.6) = -2
                // 保留decimals位小数
                const numStr = String((Math.round(num * Math.pow(10, decimal)) / Math.pow(10, decimal)).toFixed(
                    decimal)); // 四舍五入，然后固定保留2位小数
                const decimals = numStr.slice(dotIndex, dotIndex + decimal + 1); // 截取小数位
                res = String(numStr.slice(0, dotIndex)).replace(/(\d)(?=(?:\d{3})+$)/g, `$1${split}`) + decimals;
            }
            return res;
        }
    } else {
        return '--';
    }
}

/**
 * @param {Object} mobile
 * 隐藏手机号中间4位
 */
function hideMobile(mobile) {
    if (isEmpty(mobile)) {
        return '';
    }
    return mobile.substr(0, 3) + '****' + mobile.substr(mobile.length - 4);
}

/**
 * @param {Object} email
 * 隐藏邮箱敏感字符
 */
function hideEmail(email) {
    if (String(email).indexOf('@') > 0) {
        let newEmail, str = email.split('@'),
            _s = '';

        if (str[0].length > 4) {
            _s = str[0].substr(0, 4);
            for (let i = 0; i < str[0].length - 4; i++) {
                _s += '*';
            }
        } else {
            _s = str[0].substr(0, 1);
            for (let i = 0; i < str[0].length - 1; i++) {
                _s += '*';
            }
        }
        newEmail = _s + '@' + str[1];
        return newEmail;
    } else {
        return email;
    }
}

/**
 * @param {Object} arr
 * @param {Object} prop
 * 过滤数组对象重复
 */
function filterDuplicates(arr, prop) {
    // 创建一个空对象
    const lookup = {};
    // 使用Array.filter()方法遍历数组并过滤重复值
    return arr.filter(obj => {
        // 返回给定属性的布尔值
        const keep = !lookup[obj[prop]];
        // 将当前属性值设置为 true 来跟踪重复的属性值
        lookup[obj[prop]] = true;
        return keep;
    });
}

/**
 * 获取文件扩展名
 * @param {string} fileName - 文件名 
 * @returns {string} - 返回文件扩展名
 */
function getFileExt(fileName) {
    if (!fileName) {
        return '';
    }
    let ext = fileName.split('.').pop();
    return ext ? ext.toLowerCase() : '';
}

/**
 * @param {Object} str
 * @param {Object} replaceList
 * 替换字符串
 */
function replaceStrings(str, replaceList) {
    // 使用 replaceAll() 方法进行全局替换
    // 参数一：需要被替换的字符串或正则表达式
    // 参数二：用来替换的字符串
    str = str.replaceAll(/([\w-]+)\b/g, "$1");

    // 遍历 replaceList，依次进行替换
    for (let replace of replaceList) {
        // 使用 replace() 方法进行替换
        str = str.replace(new RegExp(replace, "g"), "");
    }

    return str;
}

/**
 * @param {Object} Id
 * 加密ID
 */
function encryptNumber(num) {
    let numStr = num.toString(); // 将数字转换为字符串  
    let result = '';
    let hasThreeConsecutiveDigits = numStr.length >= 3;
    if (hasThreeConsecutiveDigits) {
        result += numStr.slice(0, 3) + '***'; // 在前三个数字后面添加'***'  
        result += numStr.slice(3); // 添加剩余的数字  
    } else {
        result = numStr; // 如果字符串长度小于3，直接返回原始数字  
    }
    return result;
}

export default {
    test,
    debounce,
    throttle,
    sleep,
    random,
    guid,
    trim,
    queryParams,
    priceFormat,
    padZero,
    getProperty,
    setProperty,
    deepClone,
    deepMerge,
    randomArray,
    timeFrom,
    parseTime,
    formatTime,
    param2Obj,
    addUrlParam,
    checkIsWechatBrowser,
    inArray,
    each,
    moneyFormat,
    hideMobile,
    filterDuplicates,
    getFileExt,
    replaceStrings,
    encryptNumber
}

